#pragma once
#include "DVRCommon.h"
#include "DVRUtils.h"
#include "AABB.h"
#include <boost/endian/conversion.hpp>

namespace hiveDVR
{
	class CVolume
	{
	public:
		CVolume();
		~CVolume();

		bool loadHeader(const std::string& vFileHeader);

		size_t getWidth()  const { _ASSERTE(m_pHeader); return m_pHeader->Extents.x; }
		size_t getHeight() const { _ASSERTE(m_pHeader); return m_pHeader->Extents.y; }
		size_t getDepth()  const { _ASSERTE(m_pHeader); return m_pHeader->Extents.z; }
		std::string getVolumeName() const { _ASSERTE(m_pHeader); return m_pHeader->VolumeName; }

		uint8_t getMaxScalar() const { return m_MaxScalar; }
		uint8_t* getPtrOfScalarSet() { return m_ScalarSet.data(); }

		float sampleAverageData(CAABB& vAABB) const;

	private:
		std::vector<uint8_t> m_ScalarSet;

		hiveDVR::SVolumeInfo* m_pHeader = nullptr;

		uint8_t m_MaxScalar = 0;

		CAABB m_VolumeAABB;

		bool __loadData();
	};

	template<typename TData>
	void read(std::ifstream& vFile, TData* vStart, size_t vFileSize)
	{
		size_t BytePosition = 0;
		size_t BytesRemained = vFileSize;
		size_t MaxMemory = 100'000'000;
		while (BytesRemained > 0)
		{
			size_t Bytes2Read = std::min(BytesRemained, MaxMemory);
			vFile.read(reinterpret_cast<char*>(vStart) + BytePosition, Bytes2Read);
			if (!vFile) throw std::runtime_error("File error.");

			BytePosition += Bytes2Read;
			BytesRemained -= Bytes2Read;
		}
		vFile.clear();
	}

	template<typename TData>
	bool __loadDataImpl(const hiveDVR::SVolumeInfo* vHeader, std::vector<uint8_t>& vScalarSet)
	{
		_ASSERTE(vHeader);
		std::string AbsolutePath = vHeader->Directory + vHeader->VolumeName;
		if (AbsolutePath.empty()) return outputWarning(__EXCEPTION_SITE__, "Empty file name.");
		vScalarSet.clear();

		size_t NumVoxels = static_cast<size_t>(vHeader->Extents.x) * static_cast<size_t>(vHeader->Extents.y) * static_cast<size_t>(vHeader->Extents.z);
		size_t FileSize = NumVoxels * sizeof(TData);
		vScalarSet.resize(NumVoxels);

		std::ifstream File(AbsolutePath, std::ios::ate | std::ios::binary);
		if (!File.is_open()) return outputWarning(__EXCEPTION_SITE__, "Can not open file <" + AbsolutePath + ">.");
		size_t ActualFileSize = (size_t)File.tellg();
		if (ActualFileSize < FileSize) return outputWarning(__EXCEPTION_SITE__, "Bad volume size or wrong file.");

		std::vector<TData> ImageDataSet(NumVoxels);
		File.seekg(0, std::ios::beg);

		read(File, ImageDataSet.data(), FileSize);
		File.close();

		bool BigEndian = vHeader->Endianness == "big";
		for (TData& data : ImageDataSet)
		{
			if (BigEndian)
			{
				boost::endian::big_to_native_inplace(data);
			}
			else
			{
				boost::endian::little_to_native_inplace(data);
			}
		}

		float Min = vHeader->NormalisationRange.x;
		float Max = vHeader->NormalisationRange.y;
		std::transform(ImageDataSet.begin(), ImageDataSet.end(), vScalarSet.begin(),
			[Min, Max](TData element) -> uint8_t
		{
			float normalizedElement = (static_cast<float>(element) - Min) / (Max - Min);
			return static_cast<uint8_t>(std::numeric_limits<uint8_t>::max() * clamp(normalizedElement, 0.0f, 1.0f));
		}
		);

		return true;
	}
}